﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq.Expressions;
using System.Runtime.Serialization.Json;

namespace Samples.Radio.Agent.Data
{
    public enum PlaybackMode
    {
        Streaming,
        Podcast
    }

    public class RadioSettings : AppSettings
    {
        public readonly static RadioSettings Instance = new RadioSettings();

       
        public string CurrentStreamName
        {
            get
            {
                using(var wrapper = new MutexWrapper())
                {
                    var config = wrapper.Database.CurrentConfiguration;
                    return config.CurrentStreamName;
                }
            }
            set
            {
                using (var wrapper = new MutexWrapper())
                {
                    var config = wrapper.Database.CurrentConfiguration;
                    config.CurrentStreamName = value;
                    wrapper.Database.SubmitChanges();
                }
            }
        }

        public PlaybackMode AudioPlaybackMode
        {
            get
            {
                using (var wrapper = new MutexWrapper())
                {
                    var config = wrapper.Database.CurrentConfiguration;
                    return config.PlaybackModeIsStreaming?PlaybackMode.Streaming : PlaybackMode.Podcast;
                }
            }
            set
            {
                using (var wrapper = new MutexWrapper())
                {
                    var config = wrapper.Database.CurrentConfiguration;
                    config.PlaybackModeIsStreaming = value == PlaybackMode.Streaming;
                    wrapper.Database.SubmitChanges();
                }
            }
        }

    }

    public abstract class AppSettings
    {

        protected IDictionary<string, object> SettingsDictionary
        {
            get { return IsolatedStorageSettings.ApplicationSettings; }
        }

        /// <summary>
        /// Lock object to ensure reading/writing are synchronized
        /// </summary>
        private static readonly object LockObject = new object();

        /// <summary>
        /// An in memory dictionary containing the current values for application settings
        /// to make repeated access of a setting quicker
        /// </summary>
        private readonly Dictionary<string, object> inMemorySettings = new Dictionary<string, object>();

        protected T RetrieveSetting<T>()
        {
            return RetrieveSetting<T>(typeof (T).Name);
        }

        protected T RetrieveSetting<T>(Expression<Func<T>> propertySelector)
        {
            return RetrieveSetting<T>(SettingKeyFromProperty(propertySelector));
        }

        /// <summary>
        /// Retrieves typed settings value for a particular key
        /// </summary>
        /// <typeparam name="T">The type of the value to return. If this doesn't match
        /// the type of the value that is in the settings, or there is no settings
        /// value then the default value of type T will be returned</typeparam>
        /// <param name="settingKey">The key for the settings value to return</param>
        /// <returns>The current settings value of type T</returns>
        protected T RetrieveSetting<T>(string settingKey)
        {
            // Make sure we lock this section so that settings aren't written
            // at the same time
            lock (LockObject)
            {
                object settingValue;
                try
                {
                    // Attempt to read from the in memory settings collection
                    if (inMemorySettings.TryGetValue(settingKey, out settingValue))
                    {
                        return (T) settingValue;
                    }
                }
                catch
                {
                    // Make sure any value causing trouble is removed
                    ClearInMemorySetting(settingKey);
                }

                try
                {




                    // No value found in the in memory collection, so attempt to read from the 
                    // actual isolated storage application settings
                    if (SettingsDictionary.TryGetValue(settingKey, out settingValue))
                    {


                        // Add the retrieved value to the in memory collection for faster access next time
                        inMemorySettings[settingKey] = settingValue;

                        try
                        {
                            return (T) settingValue;
                        }
                        catch
                        {
                            // Remove the value that raised an exception being cast to the correct type
                            ClearSetting(settingKey);
                        }
                    }
                }
                catch
                {
                    // Typically an excpetion is raised when retrieving values if there is an invalid
                    // type contained in the application settings. In this case, clear and save the 
                    // settings. Unfortunately this is a last resort as it will clear any user defined
                    // settings value. It will however prevent the application from terminating
                    ClearAllSettings();
                }
                return default(T);
            }
        }

        /// <summary>
        /// Clears the specified settings value from the in memory collection
        /// </summary>
        /// <param name="settingKey">The key of the settings value to remove</param>
        protected void ClearInMemorySetting(string settingKey)
        {
            lock (LockObject)
            {
                if (inMemorySettings.ContainsKey(settingKey))
                {
                    inMemorySettings.Remove(settingKey);
                }
            }
        }

        protected void ClearInMemorySetting<T>(Expression<Func<T>> propertySelector)
        {
            ClearInMemorySetting(SettingKeyFromProperty(propertySelector));
        }

        protected void ClearInMemorySetting(Func<string> keyMethod)
        {
            ClearInMemorySetting(keyMethod());
        }

        protected void ClearSetting(string settingKey)
        {
            lock (LockObject)
            {
                try
                {
                    ClearInMemorySetting(settingKey);

                    if (SettingsDictionary.ContainsKey(settingKey))
                    {
                        SettingsDictionary.Remove(settingKey);
                    }

                }
                catch
                {
                    ClearAllSettings();
                }
            }
        }

        protected void ClearSetting<T>(Expression<Func<T>> propertySelector)
        {
            ClearSetting(SettingKeyFromProperty(propertySelector));
        }

        protected void ClearSetting(Func<string> keyMethod)
        {
            ClearSetting(keyMethod());
        }

        private string SettingKeyFromProperty<T>(Expression<Func<T>> propertySelector)
        {
            var typeName = GetType().Name;
            var memberExpression = propertySelector.Body as MemberExpression;
            if (memberExpression != null)
            {
                return typeName + ":" + memberExpression.Member.Name;
            }
            return typeName;
        }

        protected void WriteSetting<T>(Expression<Func<T>> propertySelector, T value)
        {
            WriteSetting(SettingKeyFromProperty(propertySelector), value);
        }

        protected void WriteSetting<T>(T value) where T : class
        {
            WriteSetting(typeof (T).Name, value);
        }

        /// <summary>
        /// Writes the specified value to application settings
        /// </summary>
        /// <param name="settingKey"></param>
        /// <param name="value"></param>
        protected void WriteSetting(string settingKey, object value)
        {
            lock (LockObject)
            {
                // Update the in memory cache for quick access
                inMemorySettings[settingKey] = value;

                try
                {
                    object settingsValue = value;

                    // Write to isolated storage
                    SettingsDictionary[settingKey] = settingsValue;
                    //IsolatedStorageSettings.ApplicationSettings.Save();
                }
                    // ReSharper disable EmptyGeneralCatchClause
                catch
                    // ReSharper restore EmptyGeneralCatchClause
                {
                    //Swallow this one... There seems to be some occasions when this is necessary
                }
            }
        }

        /// <summary>
        /// Indicates whether a value has been saved against a given key. 
        /// It doesn't say whether that value is null or not!
        /// </summary>
        /// <param name="settingKey">The key of the setting</param>
        /// <returns>True/False indicating whether a settings value has been set</returns>
        protected bool ValueExists(string settingKey)
        {
            lock (LockObject)
            {
                return SettingsDictionary.ContainsKey(settingKey);
            }
        }

        protected bool ValueExists<T>(Expression<Func<T>> propertySelector)
        {
            return ValueExists(SettingKeyFromProperty(propertySelector));
        }

        /// <summary>
        /// Clears all settings - not advisable, but required sometimes
        /// </summary>
        public void ClearAllSettings()
        {
            lock (LockObject)
            {
                try
                {
                    SettingsDictionary.Clear();
                    //SettingsDictionary.Save();
                }
                    // ReSharper disable EmptyGeneralCatchClause
                catch
                    // ReSharper restore EmptyGeneralCatchClause
                {
                    // Do nothing
                }
            }
        }


        private readonly IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();

        private Stream OpenFileForWrite(string fileName)
        {
            var file = store.OpenFile(fileName, FileMode.Create, FileAccess.Write);
            return file;
        }

        private void DeleteFileIfExists(string fileName)
        {
            if (store.FileExists(fileName))
            {
                store.DeleteFile(fileName);
            }
        }

        private readonly object cacheLock = new object();
        protected bool CacheToDisk(object dataToCache, string fileName, IEnumerable<Type> knownTypes = null)
        {
            try
            {
                lock (cacheLock)
                {

                    if (dataToCache == null)
                    {
                        DeleteFileIfExists(fileName);
                        return true;
                    }


                    var serializer = new DataContractJsonSerializer(dataToCache.GetType(), knownTypes);
                    using (var file = OpenFileForWrite(fileName))
                    {
                        serializer.WriteObject(file, dataToCache);
                    }
                    return true;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                Debug.WriteLine(ex.StackTrace);
                return false;
            }
        }

        private Stream OpenFileForRead(string fileName)
        {
            if (!store.FileExists(fileName)) return null;
            var file = store.OpenFile(fileName, FileMode.Open, FileAccess.Read);
            return file;
        }

        protected T LoadFromDisk<T>(string fileName, IEnumerable<Type> knownTypes = null)
        {

            try
            {
                lock (cacheLock)
                {
                    var serializer = new DataContractJsonSerializer(typeof (T), knownTypes);

                    using (var file = OpenFileForRead(fileName))
                    {
                        if (file == null) return default(T);
                        var data = serializer.ReadObject(file);
                        return (T) data;
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                Debug.WriteLine(ex.StackTrace);
                return default(T);
            }
        }


    }
}
